﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using HtmlAgilityPack;
using System.Data;
using System.Text.RegularExpressions;

namespace DataExtractionLanguage
{
    /// <summary>
    /// Represent the select statement.
    /// </summary>
    public class SelectStatement
    {
        /// <summary>
        /// Gets or sets the columns of the select statement.
        /// </summary>
        /// <remarks>These columns are may be aggregated with each other via parenthes.</remarks>
        public List<Column> Columns { get; set; }

        /// <summary>
        /// Gets all column after rebuild it to be each column alone.
        /// </summary>
        public List<Column> ActualColumns { get; private set; }

        internal void PrepareActualColumns()
        {
            ActualColumns = new List<Column>();
            foreach (Column col in Columns)
                if (col.ExtensionColumns.Count > 0)
                    BuildActualColumns(col);
                else
                    ActualColumns.Add(col);

            foreach (Column col in ActualColumns)
                if (!col.ColumnPath.StartsWith("html"))
                {
                    int tmpIndex = col.ColumnPath.IndexOf('.');
                    col.SourceAlias = col.ColumnPath.Substring(0, tmpIndex);
                    col.ColumnPath = col.ColumnPath.Substring(tmpIndex);
                }

        }
        /// <summary>
        /// Gets ActualColumns count.
        /// </summary>
        public int ActualColumnsCount
        {
            get
            {
                if (ActualColumns == null)
                    PrepareActualColumns();
                return ActualColumns.Count;
            }
        }

        /// <summary>
        /// Gets or sets the from section of the select statement.
        /// </summary>
        public FromClause From { get; set; }

        /// <summary>
        /// Gets or sets the expression of where clause.
        /// </summary>
        public ExpressionTerm WhereExp { get; set; }

        private void BuildActualColumns(Column column)
        {
            foreach (Column childColumn in column.ExtensionColumns)
            {
                childColumn.ColumnPath = string.Format("{0}.{1}", column.ColumnPath, childColumn.ColumnPath);
                if (childColumn.ExtensionColumns.Count > 0)
                    BuildActualColumns(childColumn);
                else
                    ActualColumns.Add(childColumn);
            }
        }

        private DataTable unfilteredResult = null;
        private DataRow currentRow = null;

        public DataTable Select()
        {
            unfilteredResult = From.Select();
            DataTable retVal = unfilteredResult.Clone();
            retVal.Rows.Clear();

            foreach (DataRow row in unfilteredResult.Rows)
            {
                currentRow = row;

                if (EvaluateCondition(WhereExp))
                    retVal.Rows.Add(row.ItemArray);
            }
            return retVal;
        }

        private ExpressionTerm EvaluateExpressionTerm(ExpressionTerm exp)
        {
            ExpressionTerm exp0 = exp.Clone();
            ExpressionTerm curExp = exp0;
            ExpressionTerm prev = null;
            ExpressionTerm prevPrev = null;

            while (curExp != null)
            {
                if (curExp.TermType.In(TokenTypeEnum.String, TokenTypeEnum.Number))
                {
                    prevPrev = prev;
                    prev = curExp;
                    curExp = curExp.Next;
                }
                else if (curExp.TermType == TokenTypeEnum.Term)
                {
                    DataColumn termCol = unfilteredResult.Columns[curExp.Term.ColumnAlias];
                    object cellValue = EvaluateProperty(currentRow[termCol], curExp.Term.Property);

                    if (cellValue != null)
                    {
                        if (cellValue is int)
                            curExp.TermType = TokenTypeEnum.Number;
                        else if (cellValue is string)
                            curExp.TermType = TokenTypeEnum.String;
                        else if (cellValue is bool)
                            curExp.TermType = TokenTypeEnum.Bool;

                        curExp.Value = cellValue;
                    }
                    else
                        throw new Exception("لازم تراجع هون");

                    prevPrev = prev;
                    prev = curExp;
                    curExp = curExp.Next;
                }
                else if (curExp.TermType == TokenTypeEnum.OpNot)
                {
                    if (prev == null)
                        throw new Exception("خطأ");

                    if (prev.TermType == TokenTypeEnum.Number)
                    {
                        if (prev.TermType == TokenTypeEnum.OpNot)
                            prev.Value = !Convert.ToBoolean(prev.Value);
                    }

                    prev.Next = curExp.Next;
                    curExp = prev.Next;
                }
                else if (curExp.TermType.In(TokenTypeEnum.OpPlus, TokenTypeEnum.OpMinus, TokenTypeEnum.OpMultiple, TokenTypeEnum.OpDivide, TokenTypeEnum.OpPower, TokenTypeEnum.OpAnd, TokenTypeEnum.OpOr, TokenTypeEnum.OpGreater, TokenTypeEnum.OpGreaterOrEqual, TokenTypeEnum.OpLess, TokenTypeEnum.OpLessOrEqual, TokenTypeEnum.OpEqualEqual, TokenTypeEnum.OpNotEqual))
                {
                    if (prevPrev.TermType == TokenTypeEnum.Number && prev.TermType == TokenTypeEnum.Number)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpPlus)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) + Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpMinus)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) - Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpMultiple)
                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) * Convert.ToInt32(prev.Value);
                        else if (curExp.TermType == TokenTypeEnum.OpDivide)
                        {
                            if (Convert.ToInt32(prev.Value) == 0)
                                throw new Exception("خطأ");

                            prevPrev.Value = Convert.ToInt32(prevPrev.Value) / Convert.ToInt32(prev.Value);
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpOr)
                            if (Convert.ToInt32(prevPrev.Value) == 0 && Convert.ToInt32(prev.Value) == 0)
                                prevPrev.Value = 0;
                            else
                                prevPrev.Value = 1;

                        else if (curExp.TermType == TokenTypeEnum.OpAnd)
                            if (Convert.ToInt32(prevPrev.Value) != 0 && Convert.ToInt32(prev.Value) != 0)
                                prevPrev.Value = 1;
                            else
                                prevPrev.Value = 0;

                        else if (curExp.TermType == TokenTypeEnum.OpPower)
                            prevPrev.Value = Convert.ToDecimal(Math.Pow(Convert.ToDouble(prevPrev.Value), Convert.ToDouble(prev.Value)));
                        else if (curExp.TermType == TokenTypeEnum.OpEqualEqual)
                            prevPrev.Value = Convert.ToBoolean(Convert.ToDouble(prevPrev.Value) == Convert.ToDouble(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpNotEqual)
                            prevPrev.Value = Convert.ToBoolean(Convert.ToDouble(prevPrev.Value) != Convert.ToDouble(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpGreater)
                            prevPrev.Value = Convert.ToBoolean(Convert.ToInt32(prevPrev.Value) > Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpGreaterOrEqual)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) >= Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpLess)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) < Convert.ToInt32(prev.Value));
                        else if (curExp.TermType == TokenTypeEnum.OpLessOrEqual)
                            prevPrev.Value = Convert.ToDecimal(Convert.ToInt32(prevPrev.Value) <= Convert.ToInt32(prev.Value));
                    }
                    else if (prevPrev.TermType == TokenTypeEnum.String && prev.TermType == TokenTypeEnum.String)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpEqualEqual)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value.ToString() == prev.Value.ToString());
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpPlus)
                            prevPrev.Value = prevPrev.Value.ToString() + prev.Value.ToString();
                    }
                    else if (prevPrev.TermType == TokenTypeEnum.Bool && prev.TermType == TokenTypeEnum.Bool)
                    {
                        if (curExp.TermType == TokenTypeEnum.OpAnd)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value) & Convert.ToBoolean(prev.Value);
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                        else if (curExp.TermType == TokenTypeEnum.OpOr)
                        {
                            prevPrev.Value = Convert.ToBoolean(prevPrev.Value) | Convert.ToBoolean(prev.Value);
                            prevPrev.TermType = TokenTypeEnum.Bool;
                        }
                    }

                    ExpressionTerm tmp = exp0;
                    if (prevPrev == exp0)
                        tmp = null;
                    else
                        while (tmp.Next != prevPrev)
                            tmp = tmp.Next;

                    prevPrev.Next = curExp.Next;
                    prev = prevPrev;
                    prevPrev = tmp;
                    curExp = prev.Next;
                }
            }

            return exp0;
        }

        private object EvaluateProperty(object value, PropertyMember propMember)
        {
            //HtmlNodeCollection nodes = null;
            string retVal = value.ToString();

            // 1. Evalute property.
            //switch (propMember.Property)
            //{
            //    case TokenTypeEnum.Text:
            //        retVal = htmlNode.InnerText;
            //        break;

            //    case TokenTypeEnum.GetAttribute:
            //        retVal = htmlNode.GetAttributeValue(propMember.PropertyParameter1, "");
            //        break;

            //    case TokenTypeEnum.CountOf:
            //        if (htmlNode.HasChildNodes)
            //        {
            //            nodes = htmlNode.SelectNodes(propMember.PropertyParameter1);
            //            if (nodes != null)
            //                return nodes.Count;
            //            else
            //                return 0;
            //        }
            //        else
            //            return 0;

            //    case TokenTypeEnum.ChildCount:
            //        return htmlNode.DescendantNodes().Count();
            //}

            // 2. Evalute function.
            Function funcPtr = propMember.Function;
            while (funcPtr != null)
            {
                if (funcPtr.FunctionType == TokenTypeEnum.SubString)
                {
                    ExpressionTerm startIndex = EvaluateExpressionTerm(funcPtr.StartIndexExp);
                    if (funcPtr.LengthExp == null)
                        retVal = retVal.Substring(Convert.ToInt32(startIndex.Value));
                    else
                    {
                        ExpressionTerm length = EvaluateExpressionTerm(funcPtr.LengthExp);
                        retVal = retVal.Substring(Convert.ToInt32(startIndex.Value), Convert.ToInt32(length.Value));
                    }
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.Length)
                    return retVal.Length;
                else if (funcPtr.FunctionType == TokenTypeEnum.Like)
                {
                    ExpressionTerm stringExp = EvaluateExpressionTerm(funcPtr.StringExp);
                    return retVal.Contains(stringExp.Value.ToString());
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.IndexOf)
                {
                    ExpressionTerm stringExp = EvaluateExpressionTerm(funcPtr.StringExp);
                    return retVal.IndexOf(stringExp.Value.ToString());
                }
                else if (funcPtr.FunctionType == TokenTypeEnum.Match)
                {
                    ExpressionTerm stringExp = EvaluateExpressionTerm(funcPtr.StringExp);
                    Match match = Regex.Match(retVal, stringExp.Value.ToString());
                    if (match.Success)
                        retVal = match.Value;
                    else
                        retVal = string.Empty;
                }
                else
                {
                    return retVal.Trim();
                }

                funcPtr = funcPtr.NextFunction;
            }

            return retVal;
        }

        private bool EvaluateCondition(ExpressionTerm expression)
        {
            if (expression == null)
                return true;

            ExpressionTerm whereRst = EvaluateExpressionTerm(expression);

            if (whereRst.Value is bool)
                return Convert.ToBoolean(whereRst.Value);

            return false;
        }
    }
}
